// 
// XmppSession.cs
// 
// Copyright © 2009 Jiří Zárevúcky <zarevucky.jiri@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

using System;
using System.Threading;
using System.Collections.Generic;
using Galaxium.Core;
using Galaxium.Client;
using Galaxium.Protocol.Xmpp.Library;
using Galaxium.Protocol.Xmpp.Library.Streams;
using Galaxium.Protocol.Xmpp.Library.Extensions;
using Galaxium.Protocol.Xmpp.Library.Extensions.Disco;
using Anculus.Core;

using Galaxium.Protocol.Xmpp.Library.Core;

namespace Galaxium.Protocol.Xmpp
{
	public class XmppSession: AbstractSession
	{	
		private XmppClient _client;
		private RosterWrapper _wrapper;
		
		private XmppConferenceGroup _conference_group;
		private Dictionary<JabberID, XmppConferenceContact> _conferences;
		
		public XmppClient Client { get { return _client; }}
		
		public BlockList BlockList { get; private set; }
		
		public ProtocolLog CommunicationLog { get; private set; }
		
		public override ContactCollection ContactCollection { get { return _wrapper.Contacts; }}
		public override GroupCollection   GroupCollection   { get { return _wrapper.Groups; }}

		private Socks5BytestreamManager _socks5_manager;
		private InBandBytestreamManager _ibb_manager;
		
		private Bookmarks _bookmarks;
		
		public new XmppAccount Account {
			get { return base.Account as XmppAccount; }
		}

		public XmppSession (XmppAccount account)
			:base (account)
		{
			_conference_group = new XmppConferenceGroup (this);
			_conferences = new Dictionary<JabberID, XmppConferenceContact> ();
			
			_client = new XmppClient (account.Jid);
			_client.Password = account.Password;
			
			_client.StatusChanged += HandleStatusChanged;
			_client.ConnectionStateChanged += HandleConnectionStateChanged;
			_client.ErrorOccured += HandleErrorOccured;
						
			_client.MessageManager.NewMessageEvent += HandleNewMessageEvent;

			_client.AddIdentity ("client", "pc", "Galaxium 0.8 SVN", null);
			_client.SoftwareURL = "http://code.google.com/p/galaxium/";
			
			SoftwareVersion.AttachHandler (_client, new SoftwareVersion ("Galaxium", "0.8 SVN", null));
			
			_client.AvatarChanged += HandleAvatarChanged;
		
			BlockList = new BlockList ();
			BlockList.Attach (_client);
			
			_bookmarks = new Bookmarks ();
			_bookmarks.Attach (_client);
			_bookmarks.Added += HandleBookmarkAdded;
			_bookmarks.Removed += HandleBookmarkRemoved;
			_bookmarks.Changed += HandleBookmarkChanged;
			
			_wrapper = new RosterWrapper (this, _client.Roster);
			
			CommunicationLog = new ProtocolLog (_client);
			
			var stream_listener = new StreamListener ();
			_socks5_manager = new Socks5BytestreamManager ();
			_ibb_manager = new InBandBytestreamManager ();
			stream_listener.AddBytestreamMethod (_socks5_manager);
			stream_listener.AddBytestreamMethod (_ibb_manager);
			var file_listener = new FileTransferListener ();
			file_listener.DefaultPath = "/home/jury";
			stream_listener.AddProfileListener (file_listener);
			stream_listener.Attach (_client);
			
			file_listener.TransferRequested +=
				(s, e) => ThreadUtility.Dispatch (() => HandleTransferRequested (s, e));

			Account.Presence = Account.InitialPresence;
			
			Conversations = new XmppConversationManager (this, _client);
		}

		void HandleTransferRequested (object sender, Library.Streams.FileTransferEventArgs e)
		{
			var transfer = new XmppFileTransfer (this, e.Transfer);
			var args = new FileTransferEventArgs (transfer);
			OnTransferInvitationReceived (args);
			FileTransferUtility.Add (transfer);
			ActivityUtility.EmitActivity (this, new ReceivedFileActivity (this, transfer));
		}

		private void HandleAvatarChanged(object sender, AvatarChangeEventArgs e)
		{
			ThreadUtility.Dispatch (() => {
				Account.DisplayImage = new XmppDisplayImage (e.Data);
				Account.Cache.SaveAccountImage ();
			});
		}

		private void HandleNewMessageEvent (int message_id)
		{
			var message = _client.MessageManager.GetMessage (message_id);
			if (message.IsOutgoing) return;
			
			var jid = message.Contact;
			var activity = new MessageReceivedActivity (this, GetContact (jid),
			                                            message.Subject);
			
			ThreadUtility.Dispatch (() => ActivityUtility.EmitActivity (this, activity));
		}

		private void HandleErrorOccured (object sender, ExceptionEventArgs e)
		{
			var args = new ErrorEventArgs (this, e.Exception.GetType ().Name, e.Exception.Message);
			Log.Error (e.Exception, "Error occured");
			ThreadUtility.Dispatch (() => OnErrorOccurred (args));
		}

		private void HandleStatusChanged (object sender, StatusChangeEventArgs e)
		{
			if (e.Status != Status.Error) return;

			var err_args = new ErrorEventArgs (this, "", e.Description);
			ThreadUtility.Dispatch (() => OnErrorOccurred (err_args));
		}

		void HandleConnectionStateChanged (object sender, ConnectionStateEventArgs e)
		{
			Log.Info ("XMPP: Connection state changed to: " + e.State.ToString ());
			if (e.State == ConnectionState.Connected) {
				var args = new SessionEventArgs (this);
				ThreadUtility.Dispatch (() => OnLoginCompleted (args));
			}
			else if (e.State == ConnectionState.Disconnected) {
				var args = new SessionEventArgs (this);
				ThreadUtility.Dispatch (() => {
					if (!_connecting)
						OnDisconnected (args);
				});
			}
			else {
				var message = ConnectionStateDescription.Get (e.State);
				var percent = (float) e.State / (float) ConnectionState.Connected;
				var args = new SessionProgressEventArgs (this, message, percent);
				ThreadUtility.Dispatch (() => OnLoginProgress (args));
			}

		}
		
		private bool _connecting;
		
		public override void Connect ()
		{
			_connecting = true;
			var thread = new Thread (() => {
				try {
					_client.Connect ();
				}
				catch (Exception e) {
					// FIXME: doesn't display the error in AccountWidget (why?)
					var args = new ErrorEventArgs (this, e.GetType ().Name, e.Message);
					Log.Error ("XXXX " + args.Description);
					ThreadUtility.Dispatch (() => OnErrorOccurred (args));
				}
				_connecting = false;
			});
			thread.IsBackground = true;
			thread.Start ();
		}

		public override void Disconnect ()
		{
			_connecting = false;
			_wrapper.DisableNotification = true;
			_client.Disconnect ();
		}
		
		public XmppFileTransfer SendFile (XmppContact contact, string filename)
		{
			JabberID uid = null;
			
			foreach (ResourceInfo info in contact.InternalContact) {
				if (info.Supports (Namespaces.SIFileTransfer)) {
					uid = info.FullJid;
					break;
				}
			}
			
			if (uid == null)
				throw new NotSupportedException ("Contact doesn't support file-transfers.");
			
			return SendFile (uid, filename);
		}
		
		public XmppFileTransfer SendFile (JabberID uid, string filename)
		{
			var transfer = new FileTransfer (this.Client, uid, filename, null);
			transfer.AddBytestreamMethod (_socks5_manager);
			transfer.AddBytestreamMethod (_ibb_manager);
			var xmpp_transfer = new XmppFileTransfer (this, transfer);
			OnTransferInvitationSent (new FileTransferEventArgs (xmpp_transfer));
			xmpp_transfer.Offer ();
			return xmpp_transfer;
		}

		public override IFileTransfer SendFile (IContact contact, string filename)
		{
			return SendFile (contact as XmppContact, filename);
		}

		public override void SetPresence (BasePresence presence)
		{
			SetStatus (Extensions.ToXmppStatus (presence), null);
		}

		public void SetStatus (Status status, string message)
		{
			_client.SetStatus (status, message, Account.Priority);
		}

		protected override void OnLoginCompleted (SessionEventArgs args)
		{
			Log.Warn ("########################### LOGIN COMPLETED CALLED #########################");
			Log.Warn (Environment.StackTrace);
			base.OnLoginCompleted (args);
			Account.SetStatus ();
		}
		
		public override IEntity FindEntity (string uid)
		{
			JabberID jid = uid;
			
			if (jid == null ||
			    jid == Account.Jid ||
			    jid == Account.Jid.Bare ())
				return Account;
			else
				return GetContact (jid);
		}
		
		public XmppContact GetContact (JabberID jid)
		{
			Client.Roster.CheckContact (jid); // create contact if it does not exist
			return ContactCollection.GetContact (jid.Bare ()) as XmppContact;
		}

		public void AddConference (JabberID uid, bool join)
		{
			_bookmarks.SetBookmark (uid, null, null, null, false);
		}
		
		public void RemoveConference (JabberID uid)
		{
			_bookmarks.UnsetBookmark (uid);
		}
		
		private void HandleBookmarkAdded (object sender, JidEventArgs e)
		{
			ThreadUtility.Dispatch (() => {
				if (_conferences.ContainsKey (e.UID)) return;
			
				//	var bookmark = _bookmarks.GetBookmark (e.UID);
				
				if (_conferences.Count == 0) {
					GroupCollection.Add (_conference_group);
					OnGroupAdded (new GroupEventArgs (_conference_group));
				}
				var conf = new XmppConferenceContact (this, e.UID);
				_conferences [e.UID] = conf;
				ContactCollection.Add (conf);
				_conference_group.Add (conf);
				OnContactAdded (new ContactListEventArgs (conf, _conference_group));
				//	if (join) conf.Join ();
			});
		}
		
		private void HandleBookmarkRemoved (object sender, JidEventArgs e)
		{
			ThreadUtility.Dispatch (() => {
				var uid = e.UID;
				XmppConferenceContact conf;
				if (!_conferences.TryGetValue (uid, out conf)) {
					Log.Error ("Attempting to remove non-existing conference.");
					return;
				}
				if (conf.Connected) conf.Leave ();
				OnContactRemoved (new ContactListEventArgs (conf, _conference_group));
				_conference_group.Remove (conf);
				ContactCollection.Remove (conf);
				_conferences.Remove (uid);
				if (_conferences.Count == 0) {
					OnGroupRemoved (new GroupEventArgs (_conference_group));
					GroupCollection.Remove (_conference_group);
				}
			});
		}
		
		private void HandleBookmarkChanged (object sender, JidEventArgs e)
		{
		}
	}
}
